package com.informatics.polymer.server.Polymerisation;

import java.io.IOException;
import java.util.ArrayList;

import nu.xom.Builder;
import nu.xom.Document;
import nu.xom.ParsingException;
import nu.xom.ValidityException;

import org.xmlcml.cml.element.CMLAtom;
import org.xmlcml.cml.element.CMLFragment;
import org.xmlcml.cml.element.CMLJoin;
import org.xmlcml.cml.element.CMLMolecule;

/**
 * Class that creates homopolymers.<br>
 * </br> <b>Example</b><br>
 * </br> Homopolymerisation homopolymerisation = new Homopolymerisation(8, 45.8,
 * "CC[R]", "[R]c1cccc(C[R])c1", "CCC[R]");<br>
 * </br> String homopolymer2D = homopolymerisation.generatePML();<br>
 * </br> PolymerisationUtils.add3DCoordinates(homopolymer2D);<br>
 * </br> <b>Note</b> Coordinate generation is done using external dependencies,
 * from the Chemistry Development kit<br>
 * </br> <a href="http://pele.farmbio.uu.se/nightly/api/org/openscience/cdk/modeling/builder3d/ModelBuilder3D.html"
 * >ModelBuilder3D class</a>.
 * 
 * @author Ed Cannon
 * @version 1.0
 */
public class Homopolymerisation implements Polymerisation {
	/**
	 * A double value representing the angle between repeat units.
	 */
	private double torsionAngle;
	/**
	 * Integer value representing the degree of polymerisation.
	 */
	private int degreeOfPolymerisation;
	/**
	 * CMLMolecule representation of end group 1.
	 */
	private CMLMolecule endGroup1;
	/**
	 * CMLMolecule representation of end group 2.
	 */
	private CMLMolecule endGroup2;
	/**
	 * CMLMolecule representation of the repeat unit.
	 */
	private CMLMolecule repeatUnitMolecule;
	/**
	 * CMLMolecule representation of the polymerised repeat unit.
	 */
	private CMLMolecule repeatUnitMoleculePolymerised;
	/**
	 * Maximum torsion angle allowed.
	 */
	private static final int TORSION_ANGLE_UPPER_LIMIT = 360;

	/**
	 * Constructs object to undertake homopolymerisation.
	 */
	public Homopolymerisation() {

	}

	/**
	 * Constructs object to undertake homopolymerisation using (integer) degree
	 * of polymerisation, (double) torsion angle, string representations for end
	 * groups and the repeat unit.
	 * 
	 * @param degreeOfPolymerisationValue
	 *            - an integer number of repeat units in the main chain.
	 * @param torsionAngleValue
	 *            - a double value representing the angle between the repeat
	 *            unit fragments in the main chain.
	 * @param endGroup1Value
	 *            - a SMILE string representation of end group 1.
	 * @param repeatUnitValue
	 *            - a SMILE string representation of the repeat unit.
	 * @param endGroup2Value
	 *            - a SMILE string representation of end group 2.
	 */
	public Homopolymerisation(final int degreeOfPolymerisationValue,
			final double torsionAngleValue, final String endGroup1Value,
			final String repeatUnitValue, final String endGroup2Value) {
		if (degreeOfPolymerisationValue < 1) {
			throw new IllegalArgumentException("degreeOfPolymerisationValue<1!");
		} else {
			this.degreeOfPolymerisation = degreeOfPolymerisationValue;
		}
		if (torsionAngleValue < 0) {
			throw new IllegalArgumentException("Negative torsion angle!");
		} else if (torsionAngleValue > TORSION_ANGLE_UPPER_LIMIT) {
			this.torsionAngle = Math.round(torsionAngleValue
					/ TORSION_ANGLE_UPPER_LIMIT);
		} else {
			this.torsionAngle = torsionAngleValue;
		}
		if (endGroup1Value == null) {
			this.endGroup1 = PolymerisationUtils.convertSmileString2CML(
					"[R][H]", "Eg1");
		} else {
			int rCount = PolymerisationUtils.countNumberofFragmentPoints(
					endGroup1Value, "[R]");
			if (rCount == 1) {
				this.endGroup1 = PolymerisationUtils.convertSmileString2CML(
						endGroup1Value, "Eg1");
			} else if (rCount != 1) {
				throw new IllegalArgumentException(
						"End groups are only allowed one R group!");
			}
		}

		if (repeatUnitValue == null) {
			throw new NullPointerException(
					"No repeat unit entered, please enter a repeat unit smile string");
		} else {
			int rCount = PolymerisationUtils.countNumberofFragmentPoints(
					repeatUnitValue, "[R]");
			if (rCount == 2) {
				this.repeatUnitMolecule = PolymerisationUtils
						.convertSmileString2CML(repeatUnitValue, "");
			} else if (rCount != 2) {
				throw new IllegalArgumentException(
						"Repeat units must have two R-groups!");
			}
		}

		if (endGroup2Value == null) {
			this.endGroup2 = PolymerisationUtils.convertSmileString2CML(
					"[R][H]", "Eg2");
		} else {
			int rCount = PolymerisationUtils.countNumberofFragmentPoints(
					endGroup2Value, "[R]");
			if (rCount == 1) {
				this.endGroup2 = PolymerisationUtils.convertSmileString2CML(
						endGroup2Value, "Eg2");
			} else if (rCount != 1) {
				throw new IllegalArgumentException(
						"End groups are only allowed one R group!");
			}
		}

	}

	/**
	 * Constructs object to undertake homopolymerisation using (integer) degree
	 * of polymerisation, (double) torsion angle, CMLMolecule representations
	 * for end groups and the repeat unit.
	 * 
	 * @param degreeOfPolymerisationValue
	 *            - an integer number of repeat units in the main chain.
	 * @param torsionAngleValue
	 *            - a double value representing the angle between the repeat
	 *            unit fragments in the main chain.
	 * @param endGroup1Value
	 *            - a CMLMolecule representation of end group 1.
	 * @param repeatUnitValue
	 *            - a CMLMolecule representation of the repeat unit.
	 * @param endGroup2Value
	 *            - a CMLMolecule representation of end group 2.
	 */
	public Homopolymerisation(final int degreeOfPolymerisationValue,
			final double torsionAngleValue, final CMLMolecule endGroup1Value,
			final CMLMolecule repeatUnitValue, final CMLMolecule endGroup2Value) {
		if (degreeOfPolymerisationValue < 1) {
			throw new IllegalArgumentException("degreeOfPolymerisationValue<1!");
		} else {
			this.degreeOfPolymerisation = degreeOfPolymerisationValue;
		}
		if (torsionAngleValue < 0) {
			throw new IllegalArgumentException("Negative torsion angle!");
		} else if (torsionAngleValue > TORSION_ANGLE_UPPER_LIMIT) {
			this.torsionAngle = Math.round(torsionAngleValue
					/ TORSION_ANGLE_UPPER_LIMIT);
		} else {
			this.torsionAngle = torsionAngleValue;
		}
		if (PolymerisationUtils.getRElements(endGroup1Value).size() == 1) {
			this.endGroup1 = PolymerisationUtils.addId(endGroup1Value, "Eg1");
		} else {
			throw new IllegalArgumentException(
					"End groups need to have 1 R group!");
		}
		if (PolymerisationUtils.getRElements(repeatUnitValue).size() == 2) {
			this.repeatUnitMolecule = PolymerisationUtils.addId(
					repeatUnitValue, "");
		} else {
			throw new IllegalArgumentException(
					"Repeat units must have two R groups!");
		}
		if (PolymerisationUtils.getRElements(endGroup2Value).size() == 1) {
			this.endGroup2 = PolymerisationUtils.addId(endGroup2Value, "Eg2");
		} else {
			throw new IllegalArgumentException(
					"End groups need to have 1 R group!");
		}
	}

	/**
	 * Set the repeat unit degree of polymerisation.
	 * 
	 * @param dp
	 *            - degree of polymerisation for the repeat unit
	 */
	public final void setDegreeOfPolymerisation(final int dp) {
		degreeOfPolymerisation = dp;
	}

	/**
	 * Set the torsion angle.
	 * 
	 * @param torsionAngleValue
	 *            - torsion angle between subsequent repeat units
	 * 
	 */
	public final void setTorsionAngle(final double torsionAngleValue) {
		torsionAngle = torsionAngleValue;
	}

	/**
	 * Set the CMLMolecule for end group 1.
	 * 
	 * @param eg1
	 *            - CMLMolecule representation of end group 1
	 */
	public final void setEndGroup1(final CMLMolecule eg1) {
		endGroup1 = eg1;
	}

	/**
	 * Set the CMLMolecule for the repeat unit.
	 * 
	 * @param ru
	 *            - CMLMolecule representation of repeat unit
	 */
	public final void setRepeatUnit(final CMLMolecule ru) {
		repeatUnitMolecule = ru;
	}

	/**
	 * Set the CMLMolecule for end group 2.
	 * 
	 * @param eg2
	 *            - CMLMolecule representation of end group 2
	 */
	public final void setEndGroup2(final CMLMolecule eg2) {
		endGroup2 = eg2;
	}

	/**
	 * Returns the degree of polymerisation, an integer number of repeat units
	 * in the main chain.
	 * 
	 * @return degreeOfPolymerisation
	 */
	public final int getDegreeOfPolymerisation() {
		return degreeOfPolymerisation;
	}

	/**
	 * Returns torsion angle, a double value representing the angle between the
	 * repeat unit fragments in the main chain.
	 * 
	 * @return torsionAngle
	 */
	public final double getTorsionAngle() {
		return torsionAngle;
	}

	/**
	 * Returns CMLMolecule representation of end group 1.
	 * 
	 * @return endGroup1
	 */
	public final CMLMolecule getEndGroup1() {
		return endGroup1;
	}

	/**
	 * Returns CMLMolecule representation of the repeat unit.
	 * 
	 * @return repeatUnitMolecule
	 */
	public final CMLMolecule getRepeatUnit() {
		return repeatUnitMolecule;
	}

	/**
	 * Returns CMLMolecule representation of end group 2.
	 * 
	 * @return endGroup2
	 */
	public final CMLMolecule getEndGroup2() {
		return endGroup2;
	}

	/**
	 * Returns XML string representation of the polymer built in 2D.
	 * 
	 * @return getPolymer().toXML()
	 * @see org.xmlcml.cml.tools.polymer.GeneratePML#generatePML()
	 */
	public final String generatePML() {
		return getPolymer().toXML();
	}

	/**
	 * Add 3D coordinates to built 2D polymer XML representation. Note:
	 * Coordinate generation is done using an external dependencies, from the
	 * Chemistry Development kit <a href="http://pele.farmbio.uu.se/nightly/api/org/openscience/cdk/modeling/builder3d/ModelBuilder3D.html"
	 * >ModelBuilder3D class</a>.
	 * 
	 * @param pmlStringNoCoordinates
	 *            - 2D XML file, representing the built polymer.
	 * @return XML string representation of the polymer with 3D coordinates.
	 * @throws Exception
	 *             if 3D coordinate generation fails.
	 */
	public final String add3DCoordinates(final String pmlStringNoCoordinates)
			throws Exception {
		String molWithCoords = PolymerisationUtils
				.add3DCoordinates(pmlStringNoCoordinates);
		return molWithCoords;
	}

	
/*	 public static void main(String[] args) throws Exception {
	 Homopolymerisation homopolymerisation = new Homopolymerisation(1, 45.8,
	 "CC[R]", "[R]c1cccc(C[R])c1", "CCC[R]"); 
	// CMLFragment frag =homopolymerisation.getFragment(); Document pmlBasicDoc =
	 //PolymerisationUtils.createPMLBasic(frag); Document pmlIntermediateDoc =
	 //PolymerisationUtils.createPMLIntermediate(frag);
	 //System.out.println(pmlBasicDoc.toXML());
	 //System.out.println(pmlIntermediateDoc.toXML()); //}//Document pmlBasic = /
	 CMLMolecule m = homopolymerisation.getPolymer();
	// System.out.println(m.toXML());
	// System.out.println("Atom count: "+m.getAtomCount());
	 Document cmlDoc = homopolymerisation.createCML();
	// System.out.println(cmlDoc.toXML());
	 String cmlMolecule3D = homopolymerisation.add3DCoordinates(cmlDoc.toXML());
	 System.out.println(cmlMolecule3D);
	 }
	*/ 
	/**
	 * Returns CMLMolecule representation of the built polymer.
	 * 
	 * @return Homopolymer (non-Javadoc)
	 * @see org.xmlcml.cml.tools.polymer.Polymerisation#getPolymer()
	 */
	public final CMLMolecule getPolymer() {
		//boolean buildable = PolymerisationUtils.isBuildable(getFragment());
		boolean buildable =true;
		if (buildable == true) {
			ArrayList<CMLAtom> repeatUnitR;
			if (degreeOfPolymerisation == 1) {
				// repeatUnitR =
				// PolymerisationUtils.getRElements(repeatUnitMolecule);
				repeatUnitMoleculePolymerised = repeatUnitMolecule;
				repeatUnitR = PolymerisationUtils
						.getRElements(repeatUnitMoleculePolymerised);
			} else {
				repeatUnitMoleculePolymerised = PolymerisationUtils
						.countExpression(degreeOfPolymerisation,
								repeatUnitMolecule, "");
				repeatUnitR = PolymerisationUtils
						.getRElements(repeatUnitMoleculePolymerised);
			}
			// Join up end groups with repeat unit
			CMLJoin cmlJoin = new CMLJoin();
			// Iterate through to get R-Elements and ref ID to plugin to the
			// join
			ArrayList<CMLAtom> eg1R = PolymerisationUtils
					.getRElements(endGroup1);
			ArrayList<CMLAtom> eg2R = PolymerisationUtils
					.getRElements(endGroup2);
			String[] eg1JoinValues = new String[2];
			eg1JoinValues[0] = eg1R.get(0).getId();
			eg1JoinValues[1] = repeatUnitMoleculePolymerised.getAtomById(
					repeatUnitR.get(0).getId()).getId();
			cmlJoin.setAtomRefs2(eg1JoinValues);
			cmlJoin.processMoleculeRefs2AndAtomRefs2(endGroup1,
					repeatUnitMoleculePolymerised);
			// Join repeat unit with end group 2
			CMLJoin cmlJoin2 = new CMLJoin();
			String[] eg2JoinValues = new String[2];
			eg2JoinValues[0] = repeatUnitR.get(repeatUnitR.size() - 1).getId();
			eg2JoinValues[1] = eg2R.get(0).getId();
			cmlJoin2.setAtomRefs2(eg2JoinValues);
			cmlJoin2.processMoleculeRefs2AndAtomRefs2(
					repeatUnitMoleculePolymerised, endGroup2);
			CMLFragment cf1 = new CMLFragment();
			cf1.addMolecule(endGroup1);
			cf1.addJoin(cmlJoin);
			cf1.addMolecule(repeatUnitMoleculePolymerised);
			cf1.addJoin(cmlJoin2);
			cf1.addMolecule(endGroup2);
			return PolymerisationUtils.join(endGroup1,
					repeatUnitMoleculePolymerised, endGroup2, eg1JoinValues[0],
					eg1JoinValues[1], eg2JoinValues[0], eg2JoinValues[1]);
		} else {
			UnbuildableException unbuildableException = new UnbuildableException(
					"Can't be built");
			unbuildableException.getMessage();
			return null;
		}
	}

	/**
	 * Method returns a fully atomistic CML Document representation of the
	 * polymer.
	 * 
	 * @return Document
	 */
	public final Document createCML() {
		Builder builder = new Builder();
		Document doc = null;
		try {
			doc = builder.build(repeatUnitMoleculePolymerised.toXML(), "");
		} catch (ValidityException e) {
			e.printStackTrace();
		} catch (ParsingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return doc;
	}

	/**
	 * Method to build up homopolymer fragment.
	 * 
	 * @return CMLFragment
	 */
	public final CMLFragment getFragment() {
		ArrayList<CMLAtom> eg1R = PolymerisationUtils.getRElements(endGroup1);
		ArrayList<CMLAtom> eg2R = PolymerisationUtils.getRElements(endGroup2);
		ArrayList<CMLMolecule> molecules = PolymerisationUtils
				.getRepeatUnitMolecules(degreeOfPolymerisation,
						repeatUnitMolecule, "RU");
		ArrayList<ArrayList<String>> rgroups = PolymerisationUtils
				.getRgroupConnectors(molecules);
		ArrayList<CMLMolecule> blankRepeatUnits = new ArrayList<CMLMolecule>();
		ArrayList<CMLFragment> blankFragments = new ArrayList<CMLFragment>();
		CMLMolecule endGroup1Blank = new CMLMolecule();
		endGroup1Blank.setRef("Eg1");
		CMLMolecule endGroup2Blank = new CMLMolecule();
		endGroup2Blank.setRef("Eg2");
		CMLFragment endGroup1BlankFragment = new CMLFragment();
		endGroup1BlankFragment.addMolecule(endGroup1Blank);
		CMLFragment endGroup2BlankFragment = new CMLFragment();
		endGroup2BlankFragment.addMolecule(endGroup2Blank);
		CMLFragment parentFragment = new CMLFragment();
		for (int i = 0; i < molecules.size(); i++) {
			CMLMolecule m = new CMLMolecule();
			m.setRef("RU" + Integer.toString(i));
			blankRepeatUnits.add(m);
			CMLFragment f = new CMLFragment();
			f.addMolecule(m);
			blankFragments.add(f);
		}
		// Manually add join between end group 1 and repeatUnit(0)
		CMLJoin endGroup1ToRepeatUnit = new CMLJoin();
		String[] eg1JoinValues = new String[2];
		eg1JoinValues[0] = eg1R.get(0).getId();
		eg1JoinValues[1] = rgroups.get(0).get(0);
		endGroup1ToRepeatUnit.setAtomRefs2(eg1JoinValues);
		String moleculeRefs = "PREVIOUS NEXT";
		endGroup1ToRepeatUnit.processMoleculeRefs2AndAtomRefs2(endGroup1Blank,
				molecules.get(0));
		endGroup1ToRepeatUnit.setMoleculeRefs2(moleculeRefs);
		parentFragment.appendChild(endGroup1BlankFragment);
		parentFragment.addJoin(endGroup1ToRepeatUnit);
		parentFragment.appendChild(blankFragments.get(0));
		// Try RU-RU bond
		// Loop through and create fragment joins and add fragments to
		// parentFragment
		if (molecules.size() > 1) {
			for (int i = 0; i < molecules.size(); i++) {
				if (i == 0) {
					CMLJoin ru0toru1 = new CMLJoin();
					String[] ru0Ru1 = new String[2];
					ru0Ru1[0] = rgroups.get(0).get(1);
					ru0Ru1[1] = rgroups.get(1).get(0);
					ru0toru1.setAtomRefs2(ru0Ru1);
					ru0toru1.processMoleculeRefs2AndAtomRefs2(molecules.get(i),
							molecules.get(i + 1));
					ru0toru1.setMoleculeRefs2(moleculeRefs);
					parentFragment.addJoin(ru0toru1);
					parentFragment.appendChild(blankFragments.get(i + 1));
				}
				if (i > 0 && i < molecules.size() - 1) {
					CMLJoin repeatUnit2RepeatUnit = new CMLJoin();
					repeatUnit2RepeatUnit.setAtomRefs2(rgroups.get(i).get(1)
							+ " " + rgroups.get(i + 1).get(0));
					repeatUnit2RepeatUnit.processMoleculeRefs2AndAtomRefs2(
							molecules.get(i), molecules.get(i + 1));
					repeatUnit2RepeatUnit.setMoleculeRefs2(moleculeRefs);
					parentFragment.addJoin(repeatUnit2RepeatUnit);
					parentFragment.appendChild(blankFragments.get(i + 1));
				}
			}
		}
		// Manually add last join
		CMLJoin lastRepeatUnitToEndGroup = new CMLJoin();
		lastRepeatUnitToEndGroup.setAtomRefs2(rgroups.get(rgroups.size() - 1)
				.get(1)
				+ " " + eg2R.get(0).getId());
		lastRepeatUnitToEndGroup.processMoleculeRefs2AndAtomRefs2(molecules
				.get(molecules.size() - 1), endGroup2Blank);
		lastRepeatUnitToEndGroup.setMoleculeRefs2(moleculeRefs);
		parentFragment.appendChild(blankFragments
				.get(blankFragments.size() - 1));
		parentFragment.addJoin(lastRepeatUnitToEndGroup);
		parentFragment.appendChild(endGroup2BlankFragment);
		return parentFragment;
	}

}
